package com.ibm.cps.sql.expression;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.ibm.cps.sql.expression.function.FunctionRegister;
import com.ibm.cps.sql.expression.function.scalar.ExpressionFunction;
import com.ibm.cps.sql.expression.json.FunctionNodeDeserializer;
import com.ibm.util.ErrorCode;
import com.ibm.util.exception.CPSException;

@JsonDeserialize(using = FunctionNodeDeserializer.class)
public class FunctionNode implements ValueExpression, RecursiveExpression, JsonSerializable {
	private String functionName;
	private ValueExpression[] arguments;
	@JsonIgnore
	private ExpressionFunction function;
	
	public FunctionNode() {
		//Only used for serialization
	}

	public FunctionNode(String functionName, ValueExpression[] arguments) {
		this.functionName = functionName;
		this.arguments = arguments;
	}

	@Override
	public String toString() {
		return "{\"FunctionNode\":{\"functionName\":\"" + functionName
				+ "\", \"arguments\":\"" + Arrays.toString(arguments) + "\"}}";
	}

	@Override
	public Set<String> collectVariableNames() {
		Set<String> result = new HashSet<>();
		for (ExpressionNode node : arguments) {
			result.addAll(node.collectVariableNames());
		}
		return result;
	}

	public String getFunctionName() {
		return functionName;
	}

	public ExpressionNode[] getArguments() {
		return arguments;
	}
	
	public ExpressionFunction getFunction() {
		return function;
	}

	public void setFunction(ExpressionFunction function) {
		this.function = function;
	}
	
	public void setFunctionName(String functionName) {
		this.functionName = functionName;
	}

	public void setArguments(ValueExpression[] arguments) {
		this.arguments = arguments;
	}

	@Override
	public ExpressionNode[] getChildNodes() {
		return arguments;
	}
	
	@Override
	public Object evaluate(FieldEvaluator evaluator) throws CPSException {
		if(this.function == null){
			this.function = FunctionRegister.findScalarFunction(functionName);
			if(this.function == null)
				throw new CPSException(ErrorCode.INVALID_INPUT_ERROR, "Cannot find scalar function named " + functionName);
		}
		Object[] argValues = new Object[arguments.length];
		for (int i = 0; i < arguments.length; i++) {
			argValues[i] = arguments[i].evaluate(evaluator);
		}
		return function.evaluate(argValues);
	}

	@Override
	public void updateVariableName(String original, String fullQualifiedName) {
		for (ValueExpression arg : arguments) {
			arg.updateVariableName(original, fullQualifiedName);
		}
	}

	@Override
	public void serialize(JsonGenerator jgen, SerializerProvider provider)
			throws IOException, JsonProcessingException {
		jgen.writeStartObject();
		jgen.writeStringField("functionName", functionName);
		jgen.writeArrayFieldStart("argTypes");
		for (ValueExpression arg : arguments) {
			jgen.writeString(arg.getClass().getName());
		}
		jgen.writeEndArray();
		jgen.writeArrayFieldStart("arguments");
		for (ValueExpression arg : arguments) {
			jgen.writeObject(arg);
		}
		jgen.writeEndArray();
		jgen.writeEndObject();
	}

	@Override
	public void serializeWithType(JsonGenerator jgen,
			SerializerProvider provider, TypeSerializer typeSer)
			throws IOException, JsonProcessingException {
	}
	
}
